package com.mojavelinux.open18;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import javax.persistence.Version;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.hibernate.Hibernate;
import org.hibernate.annotations.Sort;
import org.hibernate.annotations.SortType;
import org.hibernate.validator.Length;
import org.hibernate.validator.NotNull;
import org.hibernate.validator.Size;

/**
 * <p>Represents a single set of tee boxes, one for each hole on the course, that
 * is positioned to match the level of the target player.</p>
 * <p>The tee rating is a great example of a typed-entity, where USGA would
 * be one of the discriminating values.</p>
 */
@Entity
@Table( name = "tee_set", uniqueConstraints = {
	@UniqueConstraint( columnNames = { "color", "course_id" } ),
	@UniqueConstraint( columnNames = { "position", "course_id" } )
} )
public class TeeSet implements Serializable, Comparable<TeeSet> {

	private static final long serialVersionUID = 1L;

	private long id;
	
	private int version = 0;

	private String name;

	private String color;

	private int position;

	private USGARating mensRating;
	
	private USGARating ladiesRating;
	
	private Course course;

	private Set<Tee> tees;
	
	public TeeSet() {
		mensRating = new USGARating(); // perhaps just use null?
		ladiesRating = new USGARating(); // perhaps just use null?
		tees = new TreeSet<Tee>();
	}

	public TeeSet( String color ) {
		this();
		this.color = color;
	}
	
	@Id
	@GeneratedValue
	@Column( name = "id", updatable = false )
	public long getId() {
		return this.id;
	}

	public void setId( long id ) {
		this.id = id;
	}

	@Version
	@Column( name = "version" )
	public int getVersion() {
		return version;
	}

	protected void setVersion( int version ) {
		this.version = version;
	}
	
	/**
	 * Often times, tees have names associated with degree of competence (e.g.,
	 * Professional, Championship, or Amateur), or by sex and age (Men's,
	 * Ladies', Senior, etc.)
	 */
	@Column( name = "name" )
	public String getName() {
		return name;
	}

	public void setName( String name ) {
		this.name = name;
	}

	/**
	 * FIXME: color should really be an enum
	 */
	@Column( name = "color" )
	@NotNull
	@Length( max = 7 )
	public String getColor() {
		return this.color;
	}

	public void setColor( String color ) {
		this.color = color;
	}
	
	/**
	 * Get the proper name for this tee set, preferring
	 * the name attribute, but falling back to color if
	 * it does not exist.  The color is capitalized if used.
	 */
	@Transient
	public String getProperName() {
		return ( name != null ? name : StringUtils.capitalize( color ) );
	}
	
	/**
	 * The position of the tee in relation to the other
	 * tees on the course.  Lower numbers are furthur back
	 * (i.e. longer distance)
	 * 
	 * @return The relative position of the tee (the lower the number, the furthur back)
	 */
	@Column( name = "position" )
	@NotNull
	public int getPosition() {
		return position;
	}

	public void setPosition( int order ) {
		this.position = order;
	}
	
	@Embedded
	@AttributeOverrides( {
		@AttributeOverride( name = "course", column = @Column( name = "m_course_rating" ) ),
		@AttributeOverride( name = "slope", column = @Column( name = "m_slope_rating" ) )
	})
	public USGARating getMensRating() {
		return mensRating;
	}
	
	public void setMensRating( USGARating mensRating ) {
		this.mensRating = mensRating;
	}

	@Embedded
	@AttributeOverrides( {
		@AttributeOverride( name = "course", column = @Column( name = "l_course_rating" ) ),
		@AttributeOverride( name = "slope", column = @Column( name = "l_slope_rating" ) )
	})
	public USGARating getLadiesRating() {
		return ladiesRating;
	}

	public void setLadiesRating( USGARating ladiesRating ) {
		this.ladiesRating = ladiesRating;
	}

	@ManyToOne( optional = false )
	@NotNull
	public Course getCourse() {
		return this.course;
	}

	public void setCourse( Course course ) {
		this.course = course;
	}

	@OneToMany( cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "teeSet" )
	@Size( min = 0, max = 18 )
	@Sort( type = SortType.NATURAL )
	public Set<Tee> getTees() {
		return tees;
	}

	public void setTees( Set<Tee> tee ) {
		this.tees = tee;
	}
	
	public void addTee( Tee tee ) {
		tee.setTeeSet( this );
		tees.add( tee );
	}
	
	public void removeTee( Tee tee ) {
		tees.remove( tee );
	}
    
    @Transient
    public Set<Tee> getTeesOut() {
        Set<Tee> teeSubSet = new TreeSet<Tee>();
        for ( Tee tee : tees ) {
            if ( tee.getHole().getNumber() <= 9 ) {
                teeSubSet.add( tee );
            }
        }

        return teeSubSet;
    }

    @Transient
    public Set<Tee> getTeesIn() {
        Set<Tee> teeSubSet = new TreeSet<Tee>();
        for ( Tee tee : tees ) {
            if ( tee.getHole().getNumber() > 9 ) {
                teeSubSet.add( tee );
            }
        }

        return teeSubSet;
    }
    
    @Transient
    public Map<Integer, Tee> getTeesByHoleNumber() {
    	Map<Integer, Tee> teesByHoleNumber = new HashMap<Integer, Tee>();
    	for ( Tee tee : tees ) {
    		teesByHoleNumber.put( tee.getHole().getNumber(), tee );
    	}
    	
    	return teesByHoleNumber;
    }
    
    @Transient
    public int getDistanceOut() {
    	int distance = 0;
    	for ( Tee tee : tees ) {
    		if ( tee.getHole().getNumber() <= 9 ) {
    			distance += tee.getDistanceToGreen();
    		}
    	}
    	return distance;
    }
    
    @Transient
    public int getDistanceIn() {
    	int distance = 0;
    	for ( Tee tee : tees ) {
    		if ( tee.getHole().getNumber() > 9 ) {
    			distance += tee.getDistanceToGreen();
    		}
    	}
    	return distance;
    }
    
    @Transient
    public int getDistanceTotal() {
    	return getDistanceOut() + getDistanceIn();
    }
    
	public int compareTo( TeeSet other ) {
		return Integer.valueOf( position ).compareTo( Integer.valueOf( other.getPosition() ) );
	}
	
	public int hashCode() {
		final int PRIME = 31;
		int result = 1;
		result = PRIME * result + (int) ( id ^ ( id >>> 32 ) );
		// Hibernate#isInitialized() will return true if not a proxy
		if ( Hibernate.isInitialized( course ) ) {
			result = PRIME * result + ( ( course == null ) ? 0 : course.hashCode() );
		}
		result = PRIME * result + ( ( color == null ) ? 0 : color.hashCode() );
		return result;
	}

	/**
	 * Two tee sets are considered equal if both their color
	 * and course match.  If either of the two are absent, then
	 * the picture is cloudy.
	 */
	public boolean equals( Object o ) {
		if ( this == o ) {
			return true;
		}
		
		if ( !( o instanceof TeeSet ) ) {
			return false;
		}

		final TeeSet other = (TeeSet) o;
		if ( id > 0 && other.getId() > 0 ) {
			return id == other.getId();
		}
		
		if ( color == null ) {
			if ( other.color != null ) {
				return false;
			}
		}
		else if ( !color.equals( other.color ) ) {
			return false;
		}
		
		// Hibernate#isInitialized() will return true if not a proxy
		if ( Hibernate.isInitialized( course ) ) {
			if ( course == null ) {
				if ( other.course != null ) {
					return false;
				}
			}
			else if ( !course.equals( other.course ) ) {
				return false;
			}
		}
		
		return true;
	}

	public String toString() {
		return new ToStringBuilder( this, ToStringStyle.DEFAULT_STYLE ).
			append( "id", id ).
			append( "color", color ).
			append( "name", name ).
			append( "position", position ).
			toString();
	}

}
